package cn.edu.neu.lab603.cloudeval.entity

import java.util.concurrent.atomic.AtomicLong

import cn.edu.neu.lab603.cloudeval.CloudEval
import cn.edu.neu.lab603.cloudeval.entity.CloudTask.{FinishEvent, Status}
import cn.edu.neu.lab603.cloudeval.entity.model.ResModel
import cn.edu.neu.lab603.des.{SimEntity, SimEvent}
import com.typesafe.scalalogging.LazyLogging

import scala.collection.{IterableView, mutable}

/** 云计算用户需求。
  * 云计算用户需求虚拟化为一个资源使用的向量，该向量以 Map 形式存储。
  *
  * @param start      开始时间
  * @param length     经历的始终周期
  * @param resMap     资源列表
  * @param dataCenter 提交到的数据中心
  */
class CloudTask(val start: Int,
                val length: Int,
                val resMap: Map[String, ResModel],
                val dataCenter: DataCenter)
  extends SimEntity with LazyLogging {
  private implicit val self = this

  val cloudletNo: Long = CloudTask.idGen.incrementAndGet()
  CloudTask.pool(cloudletNo) = this
  /** 当前运行到的时钟。 */
  private var cursor: Int = 0

  /** 执行状态 */
  private var status = CloudTask.Status.READY

  /** 执行上下文，即虚拟机 */
  private var vm: VirtualMachine = _

  /** 所属用户 */
  private var user: User = _

  /** 设置云计算所属用户。
    *
    * @return 所属用户
    */
  def getUser: User = user

  /** 设置云计算用户。
    *
    * @param user 用户
    */
  private[entity] def setUser(user: User) = this.user = user

  /** 预计开始时间。 */
  def getPlanStartClock: Int = start

  /** 预计结束时间。 */
  def getPlanEndClock: Int = start + length

  /** 获取当前执行状态。
    *
    * @return 状态值
    */
  def getStatus: Status = status

  def receivedNetPacket(data: VirtualMachine.NetworkPacket): Unit = {}

  /** 获得资源规格（资源最大占用）。
    *
    * @return 资源规格
    */
  def getResSpecMap: Map[String, Int] = resMap.mapValues(_.getMax)

  /** 获得请求的资源。
    *
    * @return 请求的资源
    */
  def getRequestedResMap: Map[String, Int] = resMap.mapValues(_.getValue)

  /** 获取当前的程序执行时间 */
  def getCursor: Int = cursor

  /** 更新云资源的使用情况。
    *
    * @param clock 时钟周期
    */
  private[entity] def update(clock: Int) = {
    if (status == CloudTask.Status.RUNNING) {
      if (getExecutionContext == null) {
        logger.warn(s"[$clock] $this lost execution environment?")
      }

      cursor += 1

      resMap.values.foreach(_.update(cursor))
    }
    else if (status == CloudTask.Status.READY) {
      setStatus(CloudTask.Status.RUNNING)
    }

    if (cursor >= length) {
      finish(clock)
    }
  }

  /** 获得运行环境。
    *
    * @return 虚拟机
    */
  def getExecutionContext: VirtualMachine = vm

  /** 设置运行环境。
    *
    * @param vm 虚拟机
    */
  private[cloudeval] def setExecutionContext(vm: VirtualMachine) = this.vm = vm

  /** 设置执行状态。
    *
    * @param status 执行状态
    */
  private def setStatus(status: CloudTask.Status.Value) = this.status = status

  private def finish(clock: Int): Unit = {
    this.setStatus(CloudTask.Status.STOPPED)

    // 完成任务（立即任务，延时为 0）
    val context = getExecutionContext.host.context
    context.submitTask(0, new FinishEvent)
  }

  override def toString = s"Cloudlet#$cloudletNo"
}

object CloudTask {
  type Status = Status.Value
  private val idGen = new AtomicLong()
  private[entity] val pool = new scala.collection.mutable.HashMap[Long, CloudTask]

  def get(id: Long): CloudTask = pool(id)

  /** 云计算任务结束的任务。
    *
    * @param cloudlet 云计算任务
    */
  class FinishEvent(implicit val cloudlet: CloudTask) extends SimEvent[CloudEval] with LazyLogging {

    override def execute(context: CloudEval): Unit = {
      val dataCenter = cloudlet.dataCenter
      val clock = context.getCurrentClock

      dataCenter.deallocateVMForCloudlet(cloudlet)

      logger.debug(s"[$clock] $cloudlet of ${cloudlet.getExecutionContext} is finished.")
    }

  }

  /** 提交 cloudlet 的任务。
    */
  class SubmissionEvent extends SimEvent[CloudEval] with LazyLogging {

    /** 执行这个任务。
      *
      * @param context 执行环境
      */
    override def execute(context: CloudEval): Unit = {
      val clock = context.getCurrentClock
      val cloudletQueue = SubmissionEvent._cloudlets.toArray
      SubmissionEvent._cloudlets.clear()
      for (cloudlet <- cloudletQueue) {
        if (cloudlet.start <= clock) {
          val dataCenter = cloudlet.dataCenter
          try {
            val vm = if (cloudlet.getExecutionContext == null)
              dataCenter.allocateVMForCloudlet(cloudlet)
            else
              cloudlet.getExecutionContext

            if (vm.isRunning) {
              logger.debug(s"[${context.getCurrentClock}] $cloudlet is submitted on $vm of ${vm.host}.")

              // 启动云计算任务
              cloudlet.setStatus(CloudTask.Status.RUNNING)

              // 提交确认到用户层
              context.submitTaskOnNextTick(new SubmissionAckEvent(cloudlet, vm))
            } else {
              logger.debug(s"[${context.getCurrentClock}] Cloudlet#${cloudlet.cloudletNo} waited for VM#${vm.vmNo} of Host#${vm.host.hostNo}...")

              context.submitTaskOnNextTick(SubmissionEvent(cloudlet))
            }

          } catch {
            case _: NoHostToAllocateException =>
              logger.debug(s"[${context.getCurrentClock}] No enough host for cloudlet#${cloudlet.cloudletNo}, waiting...")

              context.submitTaskOnNextTick(SubmissionEvent(cloudlet))
          }
        } else {
          context.submitTask(cloudlet.start, SubmissionEvent(cloudlet))
        }
      }
    }

    override def getMaxExecTimes: Int = 1

    override def priority: Int = 1
  }

  object SubmissionEvent extends SubmissionEvent {

    private[entity] val _cloudlets = mutable.HashSet.empty[CloudTask]

    /** 获得当前 cloudlet 的视图。
      *
      * @param context 仿真上下文
      * @return
      */
    def getCurrentCloudlets(implicit context: CloudEval): IterableView[CloudTask, mutable.HashSet[CloudTask]] = _cloudlets.view.filter(_.start <= context.getCurrentClock)

    def apply(cloudlet: CloudTask): SubmissionEvent = {
      _cloudlets += cloudlet
      this
    }
  }

  /** 确认云需求申请状态。
    *
    * @param cloudlet 云计算任务
    * @param vm       虚拟机
    */
  class SubmissionAckEvent(val cloudlet: CloudTask, val vm: VirtualMachine) extends SimEvent[CloudEval] with LazyLogging {

    override def execute(context: CloudEval): Unit = {
      // 提交任务在下一个时钟周期执行
      // context.submitTaskOnNextTick(new CloudletUpdateEvent)
    }
  }

  /** 状态。 */
  object Status extends Enumeration {
    val READY, RUNNING, STOPPED = Value
  }

}

class CloudTaskExecutionException(val msg: String = "") extends Exception(msg)